/**
 *
 */
package vn.smartware.co.java.net.telnet;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.net.SocketTimeoutException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.net.telnet.TelnetClient;

import vn.smartware.co.java.net.commons.Utils;
import vn.smartware.co.java.net.exception.CommandFailedException;
import vn.smartware.co.java.net.exception.ConnetFailedException;
import vn.smartware.co.java.net.exception.DisConnetFailedException;
import vn.smartware.co.java.net.exception.LoginFailedException;
import vn.smartware.co.java.net.exception.MessageNotFoundException;

/**
 * TelnetTranfer
 * @author thuctt
 */
public class TelnetTranfer {
	
	private static Log logger = LogFactory.getLog(TelnetTranfer.class);
	
    private TelnetClient telnet = new TelnetClient();
    
    private static final String EXIT_COMMAND = "exit";

	private static final String DEFAULT_PROMPT = "$";
    
    //
    private String telnetUser = "";
    private String telnetPass = "";
    private String ipAddress = "";
    private int portNo = 23;
    private int timeout = 30;
    
    /**
     * Constructor
     * @param server
     * @param user
     * @param password
     */
    public TelnetTranfer(String server, String user, String password){

    	ipAddress = server;
    	telnetUser = user;
    	telnetPass = password;
    }
    
    /**
     * create connection to Telnet server
     * @return true/false
     */
    public boolean createConnection() {
    	
    	try{
	    	connect();
	    	login();
    	} catch (Exception e) {
    		return false;
		}
    	return true;
    }
    
	/**
	 * connect to server
	 * @throws ConnetFailedException
	 */
    private void connect() throws ConnetFailedException {
    	
    	logger.info("Telnet to server IP: " + ipAddress + "Port: " + portNo );
    	
    	try {
	    	if (portNo > 0) {
				telnet.connect(ipAddress, portNo);
	    	} else {
	    		telnet.connect(ipAddress);
	    	}
	    	
	    	telnet.setSoTimeout(this.timeout * 1000);
	    	
		} catch (IOException e) {
        	logger.error("Connect failed.");
        	throw new ConnetFailedException();
		}
		
    	logger.info("Connected.");
	    
    }
    
	/**
	 * login
	 * @throws LoginFailedException
	 */
    private void login() throws LoginFailedException {
    	
        InputStream istream = telnet.getInputStream();
        OutputStream ostream = telnet.getOutputStream();
        Reader reader = new InputStreamReader(istream);
        Writer writer = new OutputStreamWriter(ostream);
		//
    	logger.info("UserID = " + telnetUser + " / Password = " + telnetPass);
    	//
    	try {
    		readMessage(reader, ":");
        } catch (MessageNotFoundException e) {
        	throw new LoginFailedException("Login failed. MessageNotFoundException. ", e);
        } catch (IOException e) {
        	throw new LoginFailedException("Login failed. IOException. ", e);
        }
        //
        try {
		    writer.write(telnetUser + "\n");
	        writer.flush();
        } catch (IOException e) {
        	throw new LoginFailedException("Login failed. IOException. ", e);
        }
        //
        try {
        	readMessage(reader, ":");
        } catch (MessageNotFoundException e) {
        	throw new LoginFailedException("Login failed. MessageNotFoundException. ", e);
        } catch (IOException e) {
        	throw new LoginFailedException("Login failed. IOException. ", e);
        }
        //
        try {
	        writer.write(telnetPass + "\n");
	        writer.flush();
        } catch (IOException e) {
        	throw new LoginFailedException("Login failed. IOException. ", e);
        }
        //
        try {
        	readMessage(reader, DEFAULT_PROMPT);
        } catch (MessageNotFoundException e) {
        	throw new LoginFailedException("Login failed. MessageNotFoundException. ", e);
        } catch (IOException e) {
        	throw new LoginFailedException("Login failed. IOException. ", e);
        }
        
        logger.info("Login successfully.");
	    
    }

	/**
	 * send command to Telnet Server
	 * @param cmd
	 * @return String
	 * @throws CommandFailedException
	 */
    private String sendCommand(String cmd) throws CommandFailedException {
   
    	String out = null;
        InputStream istream = telnet.getInputStream();
        OutputStream ostream = telnet.getOutputStream();
        Reader reader = new InputStreamReader(istream);
        Writer writer = new OutputStreamWriter(ostream);
        
    	logger.info(">SEND " + cmd);
    	
    	try {
	        writer.write(cmd + "\n");
	        writer.flush();
	    } catch (IOException e) {
	    	throw new CommandFailedException("SEND Failed. IOException. ", e);
	    }
        
        try {
        	out = readMessage(reader, DEFAULT_PROMPT);
        	out = getResultCode(out);
        } catch (MessageNotFoundException e) {
        	throw new CommandFailedException(e.getMessage());
	    } catch (IOException e) {
	    	throw new CommandFailedException(e.getMessage());
	    }
	    
	    logger.info("Command sent.");
        
        return out;
    }

    /**
     * dispose connection
     * @return true/false
     */
    public boolean closeConnection() {
    	
    	try {
    		disConnect();
    	} catch (Exception e) {
			return false;
		}
    	return true;
    }
    
	/**
	 * disconnect from Server
	 * @throws DisConnetFailedException
	 */
    private void disConnect() throws DisConnetFailedException {
    	
        OutputStream ostream = telnet.getOutputStream();
        Writer writer = new OutputStreamWriter(ostream);

        logger.info("EXIT Server > " + EXIT_COMMAND);
        
        try {
	        writer.write(EXIT_COMMAND + "\n");
	        writer.flush();
	    } catch (IOException e) {
	    	throw new DisConnetFailedException("EXIT Command failed. IOException. ", e);
	    }
        
        try {
            telnet.disconnect();
	    } catch (IOException e) {
	    	throw new DisConnetFailedException("EXIT Command failed. IOException. ", e);
	    }
	    
	    logger.info("EXIT Successfully.");

    }
    
    /**
     * getResultCode
     * @param out
     * @return String
     */
    private String getResultCode(String out) {
    	
    	if (Utils.isNullEmpty(out)) {
    		return out;
    	}
    	//
    	String[] lines = out.split("\n");
    	if (lines.length < 2) {
    		return out;
    	}
    	return out.substring(0, out.lastIndexOf("\n"));
    }

    /**
     * readMessage
     * @param reader 
     * @param targetMessage 
     * @return String
     * @throws IOException 
     * @throws MessageNotFoundException 
     */
    private String readMessage(Reader reader, String targetMessage)
    		throws IOException, MessageNotFoundException {

        StringBuffer buffer = new StringBuffer();
        boolean isReadCheck = false;
        
        try {
	        while (true) {
	            int c = reader.read();
	            if (c < 0) {
	            	break;
	            }
	        	buffer.append((char) c);
	        	if (buffer.toString().indexOf(targetMessage) > 0) {
	        	    isReadCheck = true;
	        	    break;
	        	}
	        }
        } catch(SocketTimeoutException e) {
        	logger.error("readMessage failed. SocketTimeoutException. " + e);
        	isReadCheck = false;
        }
        
        if (!isReadCheck) {
        	throw new MessageNotFoundException();
        }
        return buffer.toString();
    }
    
    /**
	 * execCommand
     * @param command 
     * @return String
     * @throws ConnetFailedException 
     * @throws LoginFailedException 
     * @throws CommandFailedException 
     * @throws DisConnetFailedException 
	 */
	public String execCommand(String command) throws ConnetFailedException,
			LoginFailedException, CommandFailedException,
			DisConnetFailedException {
		return sendCommand(command);
	}
}
